/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 d3leaf@126.com
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.mapperdb.db;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.apache.ibatis.annotations.SelectKey;
import org.apache.ibatis.binding.MapperProxyFactory;
import org.apache.ibatis.builder.annotation.MapperAnntBuilder;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;

import com.mapperdb.builder.MapperBuilder;
import com.mapperdb.core.Assert;
import com.mapperdb.core.Const;
import com.mapperdb.db.plugin.IPlugin;
import com.mapperdb.joor.Reflect;
import com.mapperdb.mapper.Mapper;

public class ActiveRecordPlugin implements IPlugin {
	private String configName = DbKit.MAIN_CONFIG_NAME;
	private boolean started = false;
	private Config config = null;
	private SqlSessionFactory sqlSessionFactory = null;
	private Configuration configuration = null;
	private MapperBuilder mapperBuilder = null;
	private MapperAnntBuilder mapperAnntBuilder = null;
	// key -> entityClass
	private Map<Class<?>, ResultEntity> resultEntityMap = new HashMap<Class<?>, ResultEntity>();

	public ActiveRecordPlugin(SqlSessionFactory sqlSessionFactory) {
		this(DbKit.MAIN_CONFIG_NAME, sqlSessionFactory);
	}

	public ActiveRecordPlugin(String configName, SqlSessionFactory sqlSessionFactory) {
		Assert.hasLength(configName, "configName can not be blank");
		Assert.notNull(sqlSessionFactory, "SqlSessionFactory can not be null");
		this.configName = configName.trim();
		this.sqlSessionFactory = sqlSessionFactory;
		this.configuration = sqlSessionFactory.getConfiguration();
		this.mapperBuilder = new MapperBuilder(this);
		this.mapperAnntBuilder = new MapperAnntBuilder(configuration, Mapper.class);
	}

	public ActiveRecordPlugin addMapping(Class<?> entityClass) {
		return put(entityClass, new ResultEntity.Builder().entityClass(entityClass).build());
	}

	public ActiveRecordPlugin addMapping(Class<?> entityClass, Class<? extends AnntConfig> anntClass) {
		return put(entityClass, new ResultEntity.Builder()//
				.entityClass(entityClass)//
				.anntClass(anntClass)//
				.build());
	}

	public ActiveRecordPlugin addMapping(Class<?> entityClass, String tableName, String resultMapId, SelectKey selectKey) {
		return put(entityClass, new ResultEntity.Builder()//
				.entityClass(entityClass)//
				.tableName(tableName)//
				.resultMapId(resultMapId)//
				.selectKey(selectKey)//
				.configuration(configuration)//
				.build());
	}

	public Config getConfig() {
		return config;
	}

	@Override
	public boolean start() {
		if (started) return true;
		this.checkVersion();

		if (config == null) config = new Config(configName, sqlSessionFactory);
		config.resultEntityMap = resultEntityMap;
		DbKit.addConfig(config);

		// 解析 DB 需要的 MappedStatement (只缓存一次) 
		mapperAnntBuilder.afterPropertiesSet();
		mapperAnntBuilder.parse();
		config.mapperAnntBuilder = mapperAnntBuilder;
		
		started = true;
		return true;
	}

	@Override
	public boolean stop() {
		started = false;
		return true;
	}

	ActiveRecordPlugin put(Class<?> entityClass, ResultEntity resultEntity) {
		resultEntityMap.put(entityClass, resultEntity);
		return this;
	}

	void checkVersion() {
		// MyBatis v3.2.4 版本校验
		try {
			// StaticTextSqlNode 是 v3.2.4之后新增的类
			Class.forName("org.apache.ibatis.scripting.xmltags.StaticTextSqlNode");
		} catch (ClassNotFoundException e) {
			throw new RuntimeException("您使用的MyBatis版本太低，支持 MyBatis v3.2.4 及以上版本!");
		}
	}

	/**
	 * 配置完成后，执行下面的操作
	 * <br> 处理 configuration 中 全部的 MarkMapper Mapper 标记接口
	 * @param configuration
	 */
	public void processConfiguration() {
		Map<Class<?>, MapperProxyFactory<?>> knownMappers = Reflect.on(configuration)//
				.field(Const.Configuration_mapperRegistry)//
				.field(Const.MapperRegistry_knownMappers)//
				.get();
		Set<Class<?>> mapperClasses = knownMappers.keySet();
		for (Class<?> mapperClass : mapperClasses) {
			processConfiguration(mapperClass);
		}
	}

	/**
	 * 配置完成后，执行下面的操作
	 * <br> 处理 configuration 中 全部的 MarkMapper Mapper 标记接口
	 * @param configuration
	 */
	public void processConfiguration(Class<?> mapperClass) {
		this.start();
		mapperBuilder.processConfiguration(mapperClass);
	}
}
